"""Platform-aware instrumenters that use platform-specific profiles."""

from __future__ import annotations

import re
from pathlib import Path
from typing import Dict, List, Optional

from .base import InstrumentationConfig, InstrumentationPatch, Instrumenter, should_process
from .gptl import FunctionRegion, CppGPTLInstrumenter, FortranGPTLInstrumenter
from .platforms import PlatformProfile, platform_registry


class PlatformFortranInstrumenter(FortranGPTLInstrumenter):
    """Fortran instrumenter with platform-specific profile support."""

    def __init__(self, platform_profile: PlatformProfile):
        super().__init__()
        self.platform_profile = platform_profile

    def plan(self, path: Path, config: InstrumentationConfig) -> InstrumentationPatch:
        original = path.read_text()
        modified = self._instrument_text(original, config)
        description = f"Inserted {self.platform_profile.timing_library} start/stop pairs for Fortran routines"
        return InstrumentationPatch(path=path, original=original, modified=modified, description=description)

    def _plan_fortran_inserts(
        self,
        region: FunctionRegion,
        lines: List[str],
        inserts: Dict[int, List[str]],
    ) -> None:
        body_start = region.start_line + 1
        body_end = region.end_line

        if body_end <= body_start:
            return

        # Insert platform-specific include statements
        use_idx = self._locate_use_insert(body_start, body_end, lines)
        if not self._has_platform_includes(body_start, body_end, lines):
            indent = region.indent or ""
            for include_file in self.platform_profile.include_files:
                if include_file.endswith('.h'):
                    # C-style include for Fortran interface
                    inserts.setdefault(use_idx, []).append(f"{indent}  include '{include_file}'")
                else:
                    # Fortran module
                    inserts.setdefault(use_idx, []).append(f"{indent}  use {include_file}")

        # Insert start call
        start_idx = self._locate_executable_insert(body_start, body_end, lines)
        indent = region.indent or ""
        start_call = self.platform_profile.start_call_template.format(function_name=region.name)
        # Convert C-style call to Fortran
        start_call = self._adapt_call_for_fortran(start_call)
        inserts.setdefault(start_idx, []).append(f"{indent}  {start_call}")

        # Insert stop call
        stop_indent = region.indent or ""
        stop_call = self.platform_profile.stop_call_template.format(function_name=region.name)
        stop_call = self._adapt_call_for_fortran(stop_call)
        inserts.setdefault(region.end_line, []).append(f"{stop_indent}  {stop_call}")

    def _has_platform_includes(self, body_start: int, body_end: int, lines: List[str]) -> bool:
        """Check if platform includes are already present."""
        for idx in range(body_start, body_end):
            line_lower = lines[idx].strip().lower()
            for include_file in self.platform_profile.include_files:
                if f"include '{include_file}'" in line_lower or f"use {include_file}" in line_lower:
                    return True
        return False

    @staticmethod
    def _adapt_call_for_fortran(c_call: str) -> str:
        """Convert C-style call to Fortran call."""
        # Remove semicolon and convert to Fortran call syntax
        fortran_call = c_call.rstrip(';')
        # Convert function(...) to call function(...)
        if '(' in fortran_call and not fortran_call.strip().startswith('call '):
            fortran_call = f"call {fortran_call}"
        return fortran_call


class PlatformCppInstrumenter(CppGPTLInstrumenter):
    """C++ instrumenter with platform-specific profile support."""

    def __init__(self, platform_profile: PlatformProfile):
        super().__init__()
        self.platform_profile = platform_profile

    def plan(self, path: Path, config: InstrumentationConfig) -> InstrumentationPatch:
        original = path.read_text()
        modified = self._instrument_text(original, config)
        description = f"Inserted {self.platform_profile.timing_library} start/stop pairs for C/C++ functions"
        return InstrumentationPatch(path=path, original=original, modified=modified, description=description)

    def _instrument_with_function_info(self, source: str, functions: List, config: InstrumentationConfig) -> str:
        """Instrument using precise function information from AST with platform calls."""
        instrumented = source
        offset = 0

        # Sort functions by start position for proper offset handling
        sorted_functions = sorted(
            [f for f in functions if hasattr(f, 'start_line') and f.start_line > 0],
            key=lambda f: f.start_line
        )

        lines = source.splitlines()

        for func in sorted_functions:
            if not should_process(func.name, config):
                continue

            if func.name in self.CONTROL_KEYWORDS:
                continue

            # Find function in source by line number
            if func.start_line <= len(lines) and func.end_line <= len(lines):
                func_start_idx = sum(len(lines[i]) + 1 for i in range(func.start_line - 1))
                func_end_idx = sum(len(lines[i]) + 1 for i in range(func.end_line)) - 1

                # Find opening brace
                brace_pos = source.find('{', func_start_idx)
                if brace_pos == -1 or brace_pos > func_end_idx:
                    continue

                # Insert platform-specific start marker
                brace_pos += offset
                start_call = self.platform_profile.start_call_template.format(function_name=func.name)
                start_snippet = f"\n    {start_call}"
                instrumented = instrumented[:brace_pos + 1] + start_snippet + instrumented[brace_pos + 1:]
                offset += len(start_snippet)

                # Find matching closing brace
                closing_brace = self._find_matching_brace(instrumented, brace_pos + 1 + len(start_snippet))
                if closing_brace is not None:
                    stop_call = self.platform_profile.stop_call_template.format(function_name=func.name)
                    stop_snippet = f"    {stop_call}\n"
                    instrumented = instrumented[:closing_brace] + stop_snippet + instrumented[closing_brace:]
                    offset += len(stop_snippet)

        instrumented = self._ensure_platform_includes(instrumented)
        return instrumented

    def _instrument_with_regex(self, source: str, config: InstrumentationConfig) -> str:
        """Enhanced regex-based instrumentation with platform-specific calls."""
        instrumented = source
        offset = 0

        # First handle single-line functions
        single_line_matches = list(self.single_line_function_pattern.finditer(source))
        for match in single_line_matches:
            name = match.group("name")
            body = match.group("body").strip()

            if name in self.CONTROL_KEYWORDS:
                continue
            if not should_process(name, config):
                continue

            # Replace single-line function with multi-line instrumented version
            original_func = match.group(0)
            header = match.group("header")
            start_call = self.platform_profile.start_call_template.format(function_name=name)
            stop_call = self.platform_profile.stop_call_template.format(function_name=name)
            instrumented_func = f"{header}{{\n    {start_call}\n    {body}\n    {stop_call}\n}}"

            start_pos = match.start() + offset
            end_pos = match.end() + offset
            instrumented = instrumented[:start_pos] + instrumented_func + instrumented[end_pos:]
            offset += len(instrumented_func) - len(original_func)

        # Handle constructor initializer lists
        init_matches = list(self.constructor_initializer_pattern.finditer(instrumented))
        for match in init_matches:
            name = match.group("name")
            if not should_process(name, config):
                continue

            # Find the opening brace after the initializer list
            brace_pos = match.end() + offset - 1  # Position of '{'
            start_call = self.platform_profile.start_call_template.format(function_name=name)
            start_snippet = f"\n    {start_call}"
            instrumented = instrumented[:brace_pos + 1] + start_snippet + instrumented[brace_pos + 1:]
            offset += len(start_snippet)

            # Find matching closing brace
            closing_brace = self._find_matching_brace(instrumented, brace_pos + 1 + len(start_snippet))
            if closing_brace is not None:
                stop_call = self.platform_profile.stop_call_template.format(function_name=name)
                stop_snippet = f"    {stop_call}\n"
                instrumented = instrumented[:closing_brace] + stop_snippet + instrumented[closing_brace:]
                offset += len(stop_snippet)

        # Handle regular multi-line functions
        remaining_source = instrumented
        for match in reversed(single_line_matches):
            start = match.start() + offset - len(source) + len(instrumented)
            end = match.end() + offset - len(source) + len(instrumented)
            if start >= 0 and end <= len(remaining_source):
                remaining_source = remaining_source[:start] + ' ' * (end - start) + remaining_source[end:]

        matches = list(self.function_pattern.finditer(remaining_source))
        for match in matches:
            name = match.group("name")
            if name in self.CONTROL_KEYWORDS:
                continue
            if not should_process(name, config):
                continue
            if not self._has_block_body(remaining_source, match.start(), match.end()):
                continue

            # Apply platform-specific instrumentation
            body_start = match.end()
            start_call = self.platform_profile.start_call_template.format(function_name=name)
            start_snippet = f"\n    {start_call}"
            instrumented = instrumented[:body_start] + start_snippet + instrumented[body_start:]
            offset += len(start_snippet)

            brace_index = self._find_matching_brace(instrumented, body_start + len(start_snippet))
            if brace_index is None:
                continue
            stop_call = self.platform_profile.stop_call_template.format(function_name=name)
            stop_snippet = f"    {stop_call}\n"
            instrumented = instrumented[:brace_index] + stop_snippet + instrumented[brace_index:]
            offset += len(stop_snippet)

        instrumented = self._ensure_platform_includes(instrumented)
        return instrumented

    def _ensure_platform_includes(self, source: str) -> str:
        """Ensure platform-specific includes are present."""
        # Check if any platform calls are present
        has_platform_calls = any(
            call.split('(')[0].strip() in source
            for call in [self.platform_profile.start_call_template, self.platform_profile.stop_call_template]
        )

        if not has_platform_calls:
            return source

        # Check if includes are already present
        for include_file in self.platform_profile.include_files:
            if f'#include "{include_file}"' in source or f'#include <{include_file}>' in source:
                continue

            # Add missing include
            lines = source.splitlines()
            insert_idx = 0
            for idx, line in enumerate(lines):
                if line.startswith("#include"):
                    insert_idx = idx + 1

            lines.insert(insert_idx, f'#include "{include_file}"')
            source = "\n".join(lines)

        return source


def create_platform_instrumenter(language: str, platform_name: str) -> Optional[Instrumenter]:
    """Factory function to create platform-specific instrumenters."""
    profile = platform_registry.get_profile(platform_name)
    if not profile:
        raise ValueError(f"Unknown platform profile: {platform_name}")

    if language.lower() in ["fortran", "f90"]:
        return PlatformFortranInstrumenter(profile)
    elif language.lower() in ["cpp", "c++", "c"]:
        return PlatformCppInstrumenter(profile)
    else:
        raise ValueError(f"Unsupported language: {language}")


# Update the instrumenter registry
PLATFORM_INSTRUMENTER_REGISTRY = {}

for platform_name in platform_registry.list_profiles():
    for language in ["fortran", "cpp", "c++"]:
        try:
            key = (language, platform_name)
            PLATFORM_INSTRUMENTER_REGISTRY[key] = create_platform_instrumenter(language, platform_name)
        except ValueError:
            pass  # Skip unsupported combinations